package base.patterns.delegate;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import base.check.Check;
import base.patterns.operation.Function;

public class Delegate<Result, Param> {
	private Slot<Result, Param> objCall = null;
	
	public static <Result, Param> boolean isConnected(final Delegate<Result, Param> delegate){
		return delegate.isConnected();
	}

	public static <Result, Param> Result call(final Delegate<Result, Param> d, Param p){
		return d.call(p);
	}
	
	public static <Result, Param> void connect(Delegate<Result, Param> delegate, Slot<Result, Param> slot){
		Check.notNull(delegate, "delegate");
		Check.notNull(slot, "slot");
		
		delegate.connect(slot);
	}
	
	public static <Result, Param> Slot<Result, Param> disconnect(final Delegate<Result, Param> delegate, final Slot<Result, Param> slot){
		Check.isTrue(isConnected(delegate), Check.raiseException, "Delegate not connected to slot");
		
		return delegate.disconnect();
	}
	
	@SuppressWarnings("unchecked")
	public Result call(final Param parameter){
		Check.isTrue(isConnected(), Check.raiseException, "slot is not connected");
		
		try {
			
			Object res = this.objCall.call(parameter);
			return (Result) res;
		} catch (Exception e) {
			
			String error = String.format("Invoke delegate method failed: '%s'. Method '%s'", 
					e.getMessage(), objCall.toString());
			throw new RuntimeException(error , e);
		} 
	}

	
	
	
	private boolean isConnected(){
		return objCall != null;
	}
	
	private void connect(final Slot<Result, Param> slot){
		Check.notNull(slot, "slot is null");
		Check.isFalse(isConnected(), Check.raiseException, "slot is already connected");
		
		this.objCall = slot;
	}
	
	public Slot<Result, Param> disconnect(){
		if ( !isConnected() ){
			return null;
		}
		
		Slot<Result, Param> old = this.objCall; 
		this.objCall = null;
		return old;
	}

	
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface NamedMethod {
		String name();
	}
	
	
	public static class Slot<Result, Param>{
		private Function<Result, Param> callStarategy;
		
		public Slot(Function<Result, Param> call) {
			Check.notNull(call, "call");
			
			this.callStarategy = call;
		}

		public Result call(Param par) {
			return this.callStarategy.call(par);
		}
	}
	
	
}
